系统框架
第47条:熟悉系统框架
Fundation、CoreFundation、CFNetwork(封装 BSD socket,抽象成易于使用的网络接口)、CoreAudio、AVFoundation、CoreData、CoreText 等。
第48条:多用 block 枚举,少用 for 循环
遍历 collection 可以使用 for 循环
、NSEnumerator
、快速遍历法
。block 枚举
本身通过 GCD 来并发这行遍历操作,无需另外编写代码。而其它遍历方式无法轻易实现这一点。如果已知待遍历的 collection 含有何种对象,则应该修改签名,指出对象的具体类型。
第49条:对自定义内存管理语句 collection 使用无缝桥接
使用__bridge
进行桥接 Foundation 与 CoreFoundation 框架中的 C 怨言数据结构之间转换:1
2NSArray *anArray = @[@1, @2, @3];
CFArrayRef aCFArray = (__bridge CFArrayRef)(anArray);
转换涉及到的内存管理:
__bridge
:告诉ARC 如何处理 Objective-C 对象,ARC 仍然具备这个对象的所有权。__bridge_retained
:ARC 将交出对象的所有权。__bridge_transfer
:反向转换,比如将CFArrayRef
转换为NSArray *
。并令 ARC 获得对象所有权。- 使用
CoreFoundation
框架最后一定要调用与之对应的CFRelease()
。
第50条:构建缓存时选用 NSCache 而非 NSDictionary
对比:
- 当系统资源耗尽,可以自动删减缓存。使用NSDictionary需要自己 hook,在内存低警告的时候手动删减缓存。NSCache 是线程安全的,NSDictionary 不具备这种优势。
- 可以给 NSCache 对象设置上限,用于限制缓存中的对象总个数。
- 将
NSPurgeableData
对象与NSCache
搭配使用,可以实现自动清除数据的功能。也就是说,当NSPurgeableData
对象锁斩内存被系统所丢弃,该对象自身也可会从缓存中移除。
第51条:精简 initialize 与 load 的实现代码
对于加入runtime系统中的每个类和分类,都会调用这个方法,并且只调用一次。如果分类和类里面都定义了load
方法,会先调用类里的,在调用分类里的。load
方法再调用前,会加载父类的load
方法。执行该load
方法时,系统还处于“脆弱状态”(fragile state),根据某个指定的程序库,却无法判断出其中各个类的载入顺序。因此,在load
方法中使用其他类是不安全的。比如说:
1 | @interface EOCClassB |
该类不能在里面等待锁,也不要调用可能会加锁的方法。凡是在类加载之前执行某些任务的,基本都不太对。其真正的用途是仅仅在调试程序,比如可以在分类里编写此方法,用来判断该分类是否已经正确载入系统中。
想要执行与类初始化有关的操作,还要覆写+ (void)initialize
。它与+ (void)load
方法都是由 runtime 调用的,并且只调用一次。但是有一些区别:
initialize
是惰性调用的
,也就是程序用到相关的类时,才会调用。也就是说应用程序无需将每个类的initialize
都执行一遍,这与load
方法不同。对于load
,应用程序必须阻塞并等着所有类的load
都执行完,才能继续。- runtime 系统在执行该方法时,是处于正常状态的。因此,此时可以安全使用并调用任意类中的任意方法。而且 runtime 系统能确保
initialize
方法在“线程安全的环境”中执行。也就是说只有执行initialize
的那个线程可以操作类或类实例。其他线程都要先阻塞,等着initialize
执行完。 initialize
与其他消息一样,如果某个类未实现它,其父类实习那了,那么就会运行父类的实现代码。
1 | @interface EOCBaseClass: NSObject |
首次使用 EOCSubClass
时,会输出:
1 | EOCBaseClass initialize |
与其他方法一样(load 除外), initialize 也遵循常用的继承规则。所以在初始化 EOCSubClass 时,由于子类未覆写initialize
方法,因此还要把父类的实现代码再运行一遍。鉴于此通常会这样实现 initialize
方法:
1 | + (void)initialize { |
总结:
- 再加载阶段,如果类实现了 load 方法,那么系统就会调用它。类的 load 方法比分类的要先调用。与其他方法不同,load 方法不参与覆写机制。
- 首次使用某个类,系统会向其发送 initialize 消息,此方法遵从普通对象的覆写规则,所以要在初始化方法中判断初始化的是哪个类。
- load、initialize 方法应该实现的更精简一些,避免循环引用。
- 常量,基本类型可以在编译期定义。无法编译期设定的全局常量(Objective-C 对象),可以放在 initialize 方法里面初始化。
第52条:NSTimer 会保留目标对象
- NSTimer 对象会保留其目标对象,直到计数器本身失效位置,调用
invalidate
方法可令计时器失效。另外,一次性的计时器在触发完任务之后也会失效。 - 反复执行任务的计时器,容易产生循环引用,因为 NSTimer 保留其目标实例,目标实例变量又持有 NSTimer,导致循环引用。
- 扩充 NSTimer 的功能,通过 block 方式来打破循环引用。如下:
1 | // NSTimer+EOCBlockSupport.h |